﻿/*
 * We must explicity add the DEBUG and TRACE symbols so that calls
 * to System.Diagnostics.Trace are linked */
#define DEBUG
#define TRACE

using System;
using System.Diagnostics;

namespace ERP.Framework.Components.Utilities.TracingHelper
{
    /// <summary>
    ///     Provides a set of methods and properties that help you profile the execution of your code.
    ///     Currently also writes to stderr and stdout for use with NUnit.
    /// </summary>
    /// <example>
    ///     Compile the following code to produce the TraceSample.exe console application.
    ///     Include the configuration file listed below.  You should see "Hello, World!" written
    ///     to the stdout stream.
    ///     <code>
    ///         [TraceSample.exe.config]
    ///         
    ///         <![CDATA[
    ///         <?xml version="1.0" encoding="utf-8" ?> 
    ///         <configuration>
    ///             <system.diagnostics>
    ///                 <trace autoflush="true" indentsize="0">
    ///                     <listeners>
    ///                         <clear />
    ///                         <add name="ConsoleTraceListener"
    ///                              type="WW.COM.Framework.Instrumentation.Tracing.ConsoleTraceListener, WW.COM.Framework.Instrumentation.Tracing"
    ///                              initializeData=""/>
    ///                         <add name="HttpTraceListener"
    ///                              type="WW.COM.Framework.Instrumentation.Tracing.HttpTraceListener, WW.COM.Framework.Instrumentation.Tracing"
    ///                              initializeData=""/>
    ///                      </listeners>
    ///                 </trace>
    ///                 <switches>
    ///                     <!-- Values for switch WW.COM.Framework.Instrumentation.Tracing
    ///                     
    ///                     Config Value  Name     Desc
    ///                     ------------  -------  -----------------------------------------------------
    ///                     0             Off      All TraceUtil messages are disabled.
    ///                     1             Error    TraceUtil Assert is enabled
    ///                     2             Warning  TraceUtil Assert and Fail are enabled
    ///                     3             Info     TraceUtil Assert, Fail, Write, WriteIf are enabled
    ///                     4             Verbose  All TraceUtil messages are enabled (same as Info)
    ///                     -->   
    ///                     <add name="WW.COM.Framework.Instrumentation.Tracing" value="4" />
    ///                 </switches>
    ///             </system.diagnostics>
    ///         </configuration>
    ///          ]]>
    ///     </code>
    ///     
    ///     <code>
    ///         [C#]
    ///         
    ///         using System;
    ///         using WW.COM.Framework.Instrumentation.Tracing
    ///         
    ///         public static int Main()
    ///         {
    ///             int foo = 0;
    ///             int bar = 1;
    ///             
    ///             TraceUtil.Write("TraceSample::Main", "Here is a message!");
    ///             TraceUtil.Fail("TraceSample::Main", "A failure has happened.");
    ///             TraceUtil.Assert(foo == bar, "Foo should equal Bar!");
    ///             
    ///             return(0);    
    ///         }
    ///     </code>
    /// </example>
    /// <remarks>
    ///     Decorated with the <see cref="System.Diagnostics.DebuggerStepThroughAttribute"/>
    ///     as this is an instrumentation class.  Of course, you can still set a breakpoint
    ///     within the class.
    ///     <para />
    ///     <seealso cref="System.Diagnostics.TraceSwitch"/>
    /// </remarks>
    [DebuggerStepThrough()]
    public sealed class TraceUtil
    {
        #region Members

        /// <summary>Indicates if external calls to <see cref="Write(string, string)"/> should be executed.</summary>
        private static readonly bool DoTraceWrite;

        /// <summary>Indicates if external calls to <see cref="Fail(string)"/> should be executed.</summary>
        private static readonly bool DoTraceFail;

        /// <summary>Indicates if external calls to <see cref="Assert(bool, string)"/> should be executed.</summary>
        private static readonly bool DoTraceAssert;

        /// <summary>The trace switch name in the configuration file.</summary>
        private const string SWITCH_NAME = "ERP.Framework.Components.TracingHelper";

        /// <summary>Description for the switch read from the configuration file.</summary>
        private const string SWITCH_DESC = "Framework tracing filter.";

        #endregion Members

        #region Constructors

        /// <summary>
        ///     This is a utility class, thus, the constructor is declared with private
        ///     accessibility.  As a result, an instance cannot be created and only
        ///     static methods may be called.
        /// </summary>
        private TraceUtil() { }

        static TraceUtil()
        {
            // The configured filter level for tracing messages.
            TraceSwitch filter;

            string msg;

            filter = new TraceSwitch(SWITCH_NAME, SWITCH_DESC);

            if (null == filter)
            {
                msg = String.Format(@"The configuration file setting " +
                                    "//system.diagnostics/switches/add[@name='{0}']/@value " +
                                    "was set to zero (TraceLevel.None) or was not found. " +
                                    "Application tracing is now disabled.  Change this filter setting to " +
                                    "record tracing information.",
                                    SWITCH_NAME);

                // publish that filter was not recognized?

                // to help operations troubleshoot no tracing
                Trace.Write(msg);
            }
            else
            {
                msg = String.Format(@"The configuration file setting " +
                                    "//system.diagnostics/switches/add[@name='{0}']/@value " +
                                    "was set to '{1}'. ",
                                    SWITCH_NAME,
                                    filter.Level.ToString());

                Trace.Write(msg);
            }

            DoTraceWrite = filter.TraceInfo;
            DoTraceFail = filter.TraceWarning;
            DoTraceAssert = filter.TraceError;
        }

        #endregion Constructors

        /// <summary>
        ///     Checks for a condition, and displays a message if the condition is false.
        /// </summary>
        /// <param name="condition">
        ///     true to prevent a message being displayed; otherwise, false. 
        /// </param>
        /// <param name="message">
        ///     A message to write.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG")]</c>
        ///     attribute.  It is intended only for "Debug" builds, and not for "Release"
        ///     or "Retail" builds.
        ///     <para />
        ///     The the DEBUG preprocessor symbol must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG")]
        public static void Assert(bool condition, string message)
        {
            // check for condition early
            // if true, no reason to continue
            if ((TraceUtil.DoTraceAssert) && (!condition))
            {
                Trace.Assert(condition, message);
            }
        }

        /// <summary>
        ///     Checks for a condition, and displays a message if the condition is false.
        ///     The format specification is useful because it avoids expensive
        ///     object construction that can occur with a single string parameter.
        /// </summary>
        /// <param name="condition">
        ///     true to prevent a message being displayed; otherwise, false. 
        /// </param>
        /// <param name="messageFormat">
        ///     A System.String containing zero or more format specifications for
        ///     the message to write.
        /// </param>
        /// <param name="args">
        ///     An System.Object array containing zero or more objects to be formatted.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG")]</c>
        ///     attribute.  It is intended only for "Debug" builds, and not for "Release"
        ///     or "Retail" builds.
        ///     <para />
        ///     The the DEBUG preprocessor symbol must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG")]
        public static void Assert(bool condition, string messageFormat, params object[] args)
        {
            // check for condition early
            // if true, no reason to continue
            // avoid unnecessary call to String.Format
            if (!condition)
            {
                Assert(condition, String.Format(messageFormat, args));
            }
        }

        /// <summary>
        ///     Emits the specified error message.  This is useful for tracing invalid
        ///     input from a caller.  See <see cref="Assert(bool, string)"/> for checking assumptions
        ///     that should always be true within your code.
        /// </summary>
        /// <param name="message">
        ///     A message to write.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG")]</c>
        ///     attribute.  It is intended only for "Debug" builds, and not for "Release"
        ///     or "Retail" builds.
        ///     <para />
        ///     The the DEBUG preprocessor symbol must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG")]
        public static void Fail(string message)
        {
            if (TraceUtil.DoTraceFail)
            {
                Trace.Fail(message);
            }
        }

        /// <summary>
        ///     Emits the specified error message.  This is useful for tracing invalid
        ///     input from a caller.  See <see cref="Assert(bool, string)"/> for checking assumptions
        ///     that should always be true within your code.
        ///     The format specification is useful because it avoids expensive
        ///     object construction that can occur with a single string parameter.
        /// </summary>
        /// <param name="messageFormat">
        ///     A System.String containing zero or more format specifications for
        ///     the message to write.
        /// </param>
        /// <param name="args">
        ///     An System.Object array containing zero or more objects to be formatted.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG")]</c>
        ///     attribute.  It is intended only for "Debug" builds, and not for "Release"
        ///     or "Retail" builds.
        ///     <para />
        ///     The the DEBUG preprocessor symbol must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG")]
        public static void Fail(string messageFormat, params object[] args)
        {
            if (TraceUtil.DoTraceFail)
            {
                Fail(String.Format(messageFormat, args));
            }
        }

        /// <summary>
        ///     Writes a category name and a message to the trace listeners
        ///     if the preprocessor DEBUG or TRACE symbols are defined.
        /// </summary>
        /// <param name="category">
        ///     A category name used to organize the output.
        /// </param>
        /// <param name="message">
        ///     A message to write.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG"), Conditional("TRACE")]</c>
        ///     attribute.
        ///     <para />
        ///     The the DEBUG and/or TRACE preprocessor symbols must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.  For release compilations, use the compilers
        ///     define option.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG"), Conditional("TRACE")]
        public static void Write(string category, string message)
        {
            if (TraceUtil.DoTraceWrite)
            {
                message += " @ " + DateTime.Now.ToLongTimeString();
                System.Diagnostics.Trace.WriteLine(message, category);
            }
        }

        /// <summary>
        ///     Writes a category name and a message to the trace listeners
        ///     if the preprocessor DEBUG or TRACE symbols are defined.  The format
        ///     specification is useful because it avoids expensive object
        ///     construction that can occur with a single string parameter.
        /// </summary>
        /// <param name="category">
        ///     A category name used to organize the output.
        /// </param>
        /// <param name="messageFormat">
        ///     A System.String containing zero or more format specifications for
        ///     the message to write.
        /// </param>
        /// <param name="args">
        ///     An System.Object array containing zero or more objects to be formatted.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG"), Conditional("TRACE")]</c>
        ///     attribute.
        ///     <para />
        ///     The the DEBUG and/or TRACE preprocessor symbols must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.  For release compilations, use the compilers
        ///     define option.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG"), Conditional("TRACE")]
        public static void Write(string category, string messageFormat, params object[] args)
        {
            if (TraceUtil.DoTraceWrite)
            {
                Write(category, String.Format(messageFormat, args));
            }
        }

        /// <summary>
        ///     Writes a category name and a message to the trace listeners if a condition is true.
        /// </summary>
        /// <param name="condition">
        ///     true to cause a message to be written; otherwise, false . 
        /// </param>
        /// <param name="category">
        ///     A category name used to organize the output.
        /// </param>
        /// <param name="message">
        ///     A message to write.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG"), Conditional("TRACE")]</c>
        ///     attribute.
        ///     <para />
        ///     The the DEBUG and/or TRACE preprocessor symbols must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.  For release compilations, use the compilers
        ///     define option.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG"), Conditional("TRACE")]
        public static void WriteIf(bool condition, string category, string message)
        {
            // short circuit evaluate condition and whether tracing is enabled
            if (condition && TraceUtil.DoTraceWrite)
            {
                Write(category, message);
            }
        }

        /// <summary>
        ///     Writes a category name and a message to the trace listeners if
        ///     a condition is true and the preprocessor DEBUG or TRACE symbols
        ///     are defined.  The format specification is useful because it avoids
        ///     expensive object construction that can occur
        ///     with a single string parameter.
        /// </summary>
        /// <param name="condition">
        ///     true to cause a message to be written; otherwise, false . 
        /// </param>
        /// <param name="category">
        ///     A category name used to organize the output.
        /// </param>
        /// <param name="messageFormat">
        ///     A System.String containing zero or more format specifications for
        ///     the message to write.
        /// </param>
        /// <param name="args">
        ///     An System.Object array containing zero or more objects to be formatted.
        /// </param>
        /// <remarks>
        ///     This method is decorated with the <c>[Conditional("DEBUG"), Conditional("TRACE")]</c>
        ///     attribute.
        ///     <para />
        ///     The the DEBUG and/or TRACE preprocessor symbols must be defined when compiling the
        ///     calling code, otherwise the call is omitted.  Enable the compiler debug
        ///     option to automatically define DEBUG.  For release compilations, use the compilers
        ///     define option.
        ///     <para />
        ///     <seealso cref="System.Diagnostics.ConditionalAttribute"/>
        /// </remarks>
        [Conditional("DEBUG"), Conditional("TRACE")]
        public static void WriteIf(bool condition, string category, string messageFormat, params object[] args)
        {
            // check for condition here, rather than in overload of WriteIf
            // to avoid unnecessary call to String.Format
            // short circuit evaluate condition and whether tracing is enabled
            if (condition && TraceUtil.DoTraceWrite)
            {
                WriteIf(condition, category, String.Format(messageFormat, args));
            }
        }
    }
}


